package com.kele.base.core;

import com.kele.base.BasePage;
import com.kele.base.annotation.Unique;
import com.kele.base.bo.BaseBO;
import com.kele.base.entity.BaseEntity;
import com.kele.base.util.GenericTypeUtil;
import com.kele.base.util.MapstructUtils;
import jakarta.annotation.PostConstruct;
import jakarta.persistence.criteria.*;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.data.domain.Page;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Stream;

public abstract class BaseServiceImpl<ENTITY extends BaseEntity<ID>, ID extends Serializable, BO extends BaseBO<ID>, VO> implements BaseService<ENTITY, ID, BO, VO>{

    private final Logger logger = LoggerFactory.getLogger(BaseServiceImpl.class);

    private final BaseRepository<ENTITY, ID> repository;

    public BaseServiceImpl(BaseRepository<ENTITY, ID> repository) {
        this.repository = repository;
    }

    private final static String[] IGNORE_PROPERTY = {"createUserId", "createTime", "updateUserId", "updateTime"};

    private List<Field> uniqueFieldList;

    private Class<ENTITY> entityClass;

    private Class<VO> voClass;

    /**
     * 初始化方法，获取带有唯一标识注解的字段集合，获取实体类类型
     * @see Unique
     */
    @PostConstruct
    private void init(){
        uniqueFieldList = FieldUtils.getFieldsListWithAnnotation(GenericTypeUtil.getParameterizedClass(getClass(), 0), Unique.class);
        entityClass = GenericTypeUtil.getParameterizedClass(getClass(), 0);
        voClass = GenericTypeUtil.getParameterizedClass(getClass(), 3);
    }

    /**
     * 转换单个bo对象为实体对象
     * @param bo 业务对象
     * @return 实体对象
     */
    private ENTITY convertEntity(BO bo){
        ENTITY entity = MapstructUtils.convert(bo, entityClass);
        Assert.notNull(entity, "Entity conversion failed.");
        return entity;
    }

    /**
     * 转换bo对象列表为实体对象列表
     * @param boList 业务对象列表
     * @return 实体对象列表
     */
    private List<ENTITY> convertEntityList(List<BO> boList){
        List<ENTITY> entityList = MapstructUtils.convert(boList, entityClass);
        Assert.notNull(entityList, "Entity conversion failed.");
        return entityList;
    }

    @Override
    public ENTITY getById(ID id) {
        Assert.notNull(id, "id不能为空");

        return repository.getReferenceById(id);
    }

    @Override
    public VO getVoById(ID id) {
        Assert.notNull(id, "id不能为空");
        ENTITY referenceById = repository.getReferenceById(id);
        return MapstructUtils.convert(referenceById, voClass);
    }

    @Override
    public Page<VO> getPageVO(BO bo, BasePage pageQuery) {
        Page<ENTITY> all = repository.findAll(buildQueryPredicate(bo), pageQuery.buildPageRequest());
        return all.map(entity -> MapstructUtils.convert(entity, voClass));
    }

    /**
     * 构造查询条件
     * 子类可以重写该方法，自定义查询条件
     * @param bo 查询条件参数
     * @return 查询条件
     */
    public abstract com.querydsl.core.types.Predicate buildQueryPredicate(BO bo);

    @Override
    @Transactional
    public ID add(BO bo) {
        Assert.isNull(bo.getId(), "添加信息时,id必需为空");

        ENTITY entity = convertEntity(bo);

        checkUnique(entity);

        repository.save(entity);

        return entity.getId();
    }

    @Override
    @Transactional
    @Deprecated
    public void addBatch(List<BO> boList) {
        if (CollectionUtils.isEmpty(boList)){
            return;
        }

        Assert.isTrue(boList.stream().anyMatch(entity -> Objects.nonNull(entity.getId())), "添加信息时,id必需为空");

        List<ENTITY> entityList = convertEntityList(boList);

        repository.saveAll(entityList);
    }



    @Override
    @Transactional
    public ID edit(BO bo) {
        Assert.notNull(bo.getId(), "更新信息时,id不能为空");

        ENTITY entity = convertEntity(bo);

        checkUnique(entity);

        ENTITY oldEntity = repository.getReferenceById(entity.getId());

        BeanUtils.copyProperties(entity, oldEntity, getNullPropertyNames(entity));

        repository.save(oldEntity);

        return entity.getId();
    }

    @Override
    @Transactional
    @Deprecated
    public void editBatchById(List<BO> boList) {
        if (CollectionUtils.isEmpty(boList)){
            return;
        }

        Assert.isTrue(boList.stream().anyMatch(entity -> Objects.isNull(entity.getId())), "修改信息时,id不能为空");

        List<ENTITY> entityList = convertEntityList(boList);

        repository.saveAll(entityList);
    }

    @Override
    @Transactional
    public void delete(ID id) {
        Assert.notNull(id, "删除信息时,id不能为空");

        repository.deleteById(id);
    }

    @Override
    @Transactional
    public void delete(Collection<ID> idCollection) {
        Assert.isTrue(CollectionUtils.isEmpty(idCollection), "批量删除信息时,id集合不能为空");

        repository.deleteAllByIdInBatch(idCollection);
    }

    private String[] getNullPropertyNames(Object source) {
        BeanWrapper sourceWrapper = new BeanWrapperImpl(source);
        String[] nullPropertyNames = Stream.of(sourceWrapper.getPropertyDescriptors())
                .map(PropertyDescriptor::getName)
                .filter(propertyName -> {
                    Object value = sourceWrapper.getPropertyValue(propertyName);
                    if (sourceWrapper.getPropertyType(propertyName) == String.class) {
                        return !StringUtils.hasText((String) value);
                    } else {
                        return value == null;
                    }
                })
                .toArray(String[]::new);
        return Stream.concat(Arrays.stream(nullPropertyNames), Arrays.stream(IGNORE_PROPERTY)).toArray(String[]::new);
    }

    /**
     * 字段唯一性检查
     * @param entity 实体
     */
    private void checkUnique(ENTITY entity){
        if (CollectionUtils.isEmpty(uniqueFieldList)){
            return;
        }
        // 循环带有唯一标识注解的字段集合
        for (Field field : uniqueFieldList) {
            // 拼装查询条件
            Specification<ENTITY> condition;
            try {
                condition = new Specification<ENTITY>() {
                    @Override
                    public Predicate toPredicate(Root<ENTITY> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
                        Path<Object> fieldPath = root.get(field.getName());
                        Object fieldValue;
                        try {
                            field.setAccessible(true);
                            fieldValue = field.get(entity);
                        } catch (IllegalAccessException e) {
                            logger.error("Failed to get the value of the unique field.", e);
                            throw new RuntimeException("Uniqueness check is abnormal.", e);
                        }
                        if (Objects.isNull(fieldValue)){
                            // 唯一性检查字段为空
                            throw new IllegalArgumentException("Unique field is null.");
                        }
                        // 添加一条查询条件, where 后加上 fieldPath = fieldValue
                        return criteriaBuilder.and(criteriaBuilder.equal(fieldPath, fieldValue));
                    }
                };
            } catch (IllegalArgumentException e) {
                logger.error(e.getMessage() + " Skip this check.", e);
                continue;
            }

            // 查询
            Optional<ENTITY> one = repository.findOne(condition);

            if (one.isPresent()) {
                // 如果存在并且与当前实体id不一致，则不唯一
                if (! one.get().getId().equals(entity.getId())) {
                    String message = field.getAnnotation(Unique.class).message();
                    String errorMessage = StringUtils.hasText(message) ? message : field.getName() + " must be unique!";
                    throw new IllegalArgumentException(errorMessage);
                }
            }
        }
    }
}
